﻿#region Header
//
//	Project:		AllegroNET
//	Description:	Silverlight port of Allegro (http://alleg.sourceforge.net/)
//
//	Datafile reading routines
//
//	Copyright © 2010 Andrey Gayvoronsky
//
//	Please read the License.txt for details.
//
#endregion

using System;
using System.Collections.Generic;

namespace AllegroNET
{
	public delegate void datafile_callback(DATAFILE dat_obj);	//callback that will occur with loading of each object
	public delegate object datafile_object_loader_callback(PACKFILE f, int size);	//callback for loading custom type object
	public delegate void datafile_object_destroyer_callback(object obj);	//callback for destroying custom type object

	public class DATAFILE_INDEX
	{
		public string filename;		// datafile name (path)
		public long[] offset;		// list of offsets
	};

	public class DATAFILE_PROPERTY
	{
		public int type;		// property type
		public string dat;		// property value
	}

	public class DATAFILE : IDisposable
	{
		public object dat;							// actual object
		public int type;							// object type 
		public long size;							// size of the object 
		public List<DATAFILE_PROPERTY> prop;		// object properties

		#region IDisposable Members

		private bool isDisposed = false;
		~DATAFILE()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		protected virtual void Dispose(bool disposing)
		{
			if (!isDisposed)
			{
				if (disposing)
				{
					dat = null;
					prop = null;
				}

				isDisposed = true;
			}
		}
		#endregion
	}

	public partial class Allegro
	{
		private static int DAT_MAGIC = DAT_ID("ALL.");
		private static int DAT_FILE = DAT_ID("FILE");
		private static int DAT_DATA = DAT_ID("DATA");
		private static int DAT_FONT = DAT_ID("FONT");
		private static int DAT_SAMPLE = DAT_ID("SAMP");
		private static int DAT_MIDI = DAT_ID("MIDI");
		private static int DAT_PATCH = DAT_ID("PAT ");
		private static int DAT_FLI = DAT_ID("FLIC");
		private static int DAT_BITMAP = DAT_ID("BMP ");
		private static int DAT_RLE_SPRITE = DAT_ID("RLE ");
		private static int DAT_C_SPRITE = DAT_ID("CMP ");
		private static int DAT_XC_SPRITE = DAT_ID("XCMP");
		private static int DAT_PALETTE = DAT_ID("PAL ");
		private static int DAT_PROPERTY = DAT_ID("prop");
		private static int DAT_NAME = DAT_ID("NAME");
		private static int DAT_END = -1;

		private static int F_PACK_MAGIC = DAT_ID("slh!");	// signature for compressed packfiles 
		private static int F_NOPACK_MAGIC = DAT_ID("slh.");	// signature for uncompressed packfiles

		private static Dictionary<int, datafile_object_loader_callback> datatypes_loader = new Dictionary<int, datafile_object_loader_callback>()
		{
			{DAT_BITMAP, load_bitmap_object},
			{DAT_RLE_SPRITE, load_rle_sprite_object},
			{DAT_FILE, load_file_object},
			//{DAT_SAMPLE, load_sample_object}
		};

		private static Dictionary<int, datafile_object_destroyer_callback> datatypes_destroyer = new Dictionary<int, datafile_object_destroyer_callback>()
		{
			{DAT_BITMAP, destroy_basic_types},
			{DAT_RLE_SPRITE, destroy_basic_types},
			//{DAT_SAMPLE, destroy_basic_types}
		};

		static public int DAT_ID(char a, char b, char c, char d)
		{
			return AL_ID(a, b, c, d);
		}

		static public int DAT_ID(string id)
		{
			if (id.Length < 4)
				throw new Exception("Wront parameter for DAT_ID()");

			return AL_ID(id[0], id[1], id[2], id[3]);
		}

		/* load_datafile:
		 *  Loads an entire data file into memory, and returns a pointer to it. 
		 *  On error, sets errno and returns NULL.
		 *  
		 * Note1: encrypted files is not supported
		 * Note2: Only global compression is supported. I.e. compressed objects is not supported
		 */

		static public DATAFILE[] load_datafile(string filename, datafile_callback callback = null)
		{
			if (filename.Length < 0)
				return null;

			DATAFILE[] dat = null;
			PACKFILE pf = pack_fopen(filename, "r");

			if (pf != null)
			{
				dat = load_file_objects(pf, callback);
				pf.Dispose();
			}

			return dat;
		}

		/* load_property:
		 *  Helper to load a property from a datafile and store it in 'prop'.
		 *  Returns 0 on success and -1 on failure.
		 */
		static private DATAFILE_PROPERTY load_property(PACKFILE f)
		{
			try
			{
				int type = (int)pack_mgetl(f);
				int size = (int)pack_mgetl(f);

				byte[] buff = new byte[size + 1];

				//C# already creates zero-filled arrays, so no need to set zero byte manually
				pack_fread(buff, size, f);

				DATAFILE_PROPERTY prop = new DATAFILE_PROPERTY();

				prop.type = type;
				prop.dat = System.Text.Encoding.UTF8.GetString(buff, 0, size);
				return prop;
			}
			catch (Exception)
			{
				return null;
			}
		}

		/* read_sample:
		 *  Reads a sample from a file.
		 */
		/*
		static private SAMPLE read_sample(PACKFILE f)
		{
			short bits;
			SAMPLE s = new SAMPLE();

			bits = (short)pack_mgetw(f);

			if (bits < 0)
			{
				s.bits = -bits;
				s.stereo = true;
			}
			else
			{
				s.bits = bits;
				s.stereo = false;
			}

			s.freq = (int)pack_mgetw(f);
			s.len = (int)pack_mgetl(f);
			s.priority = 128;
			s.loop_start = 0;
			s.loop_end = s.len;
			s.param = 0;

			int len = s.len * ((s.stereo) ? 2 : 1);

			if (s.bits == 8)
			{
				s.data = new byte[len];
				pack_fread(s.data, len, f);
			}
			else
			{
				len = sizeof(short) * len;
				s.data = new byte[len];
				byte a, b;

				for (int i = 0; i < len; )
				{
					a = (byte)pack_getc(f);
					b = (byte)pack_getc(f);

					s.data[i++] = b;
					s.data[i++] = a;
				}
			}

			return s;
		}
		*/
		/* read_bitmap:
		 *  Reads a bitmap from a file, allocating memory to store it.
		 */
		static private BITMAP read_bitmap(PACKFILE f, int bits, bool allowconv)
		{
			BITMAP bmp = null;
			bool alpha = false;

			if (bits < 0)
			{
				bits = -bits;
				alpha = true;
			}

			int w = pack_mgetw(f);
			int h = pack_mgetw(f);
			int a, r, g, b;
			int ptr;
			int x, y, c;

			bmp = create_bitmap(w, h);

			switch (bits)
			{
				case 4: goto case 8;
				case 8: throw new Exception("4bit/8bit image is not supported for loading!");
				case 15: goto case 16;
				case 16:
					// 15bit/16bit hicolor
					for (y = 0; y < h; y++)
					{
						ptr = bmp.line[y];
						for (x = 0; x < w; x++)
						{
							c = Allegro.pack_igetw(f);
							r = _rgb_scale_5[(c >> 11) & 0x1F];
							g = _rgb_scale_6[(c >> 5) & 0x3F];
							b = _rgb_scale_5[c & 0x1F];
							bmp.dat[ptr + x] = makeacol32(r, g, b, 0);
						}
					}
					break;
				case 24: goto case 32;
				case 32:
					// 24bit/32bit rgba
					for (y = 0; y < h; y++)
					{
						ptr = bmp.line[y];

						for (x = 0; x < w; x++)
						{
							r = pack_getc(f);
							g = pack_getc(f);
							b = pack_getc(f);

							if (alpha)
								a = pack_getc(f);
							else
								a = 0;

							bmp.dat[ptr + x] = makeacol32(r, g, b, a);
						}
					}
					break;
			}

			return bmp;
		}

		/* read_rle_sprite:
		 *  Reads an RLE compressed sprite from a file, allocating memory for it. 
		 */
		static private RLE_SPRITE read_rle_sprite(PACKFILE f, int bits)
		{
			RLE_SPRITE s = null;
			bool alpha = false;

			if (bits < 0)
			{
				bits = -bits;
				alpha = true;
			}

			int w = pack_mgetw(f);
			int h = pack_mgetw(f);
			int size = (int)pack_mgetl(f);

			s = new RLE_SPRITE();
			s.w = w;
			s.h = h;
			s.size = size;
			s.dat = new int[size];

			int eol_marker = MASK_COLOR_32;
			int dat_ptr = 0;
			int c32, x;
			int r, g, b, a;
			short c16;

			switch (bits)
			{
				case 8: throw new Exception("8bit RLE sprite is not supported for loading!");
				case 15: goto case 16;
				case 16:
					// read hicolor data
					for (int y = 0; y < h; y++)
					{
						c16 = (short)pack_igetw(f);

						while ((ushort)c16 != MASK_COLOR_16)
						{
							if (c16 < 0)
							{
								// skip count 
								s.dat[dat_ptr++] = c16;
							}
							else
							{
								// solid run 
								x = c16;
								s.dat[dat_ptr++] = c16;

								while (x-- > 0)
								{
									c16 = (short)pack_igetw(f);
									r = _rgb_scale_5[(c16 >> 11) & 0x1F];
									g = _rgb_scale_6[(c16 >> 5) & 0x3F];
									b = _rgb_scale_5[c16 & 0x1F];
									s.dat[dat_ptr++] = makeacol32(r, g, b, 0);
								}
							}

							c16 = (short)pack_igetw(f);
						}

						// end of line 
						s.dat[dat_ptr++] = eol_marker;
					}
					break;

				case 24: goto case 32;
				case 32:
					// read truecolor data 
					for (int y = 0; y < h; y++)
					{
						c32 = (int)pack_igetl(f);

						while ((uint)c32 != MASK_COLOR_32)
						{
							if (c32 < 0)
							{
								// skip count 
								s.dat[dat_ptr++] = c32;
							}
							else
							{
								// solid run 
								x = c32;
								s.dat[dat_ptr++] = c32;

								while (x-- > 0)
								{
									r = pack_getc(f);
									g = pack_getc(f);
									b = pack_getc(f);

									if (alpha)
										a = pack_getc(f);
									else
										a = 0;

									s.dat[dat_ptr++] = makeacol32(r, g, b, a);
								}
							}

							c32 = (int)pack_igetl(f);
						}

						// end of line 
						s.dat[dat_ptr++] = eol_marker;
					}

					break;
			}

			return s;
		}


		/* load_bitmap_object:
		 *  Loads a bitmap object from a datafile.
		 */
		static private object load_bitmap_object(PACKFILE f, int size)
		{
			short bits = (short)pack_mgetw(f);
			return read_bitmap(f, bits, true);
		}

		/* load_rle_sprite_object:
		 *  Loads an RLE sprite object from a datafile.
		 */
		static private object load_rle_sprite_object(PACKFILE f, int size)
		{
			short bits = (short)pack_mgetw(f);
			return read_rle_sprite(f, bits);
		}

		/* load_file_object:
		 *  Loads a datafile object.
		 */
		static private object load_file_object(PACKFILE f, int size)
		{
			return load_file_objects(f, null);
		}

		/* load_sample_object:
		 *  Loads a sample object from a datafile.
		 */
		/*static private object load_sample_object(PACKFILE f, int size)
		{
			return read_sample(f);
		}
		*/
		/* load_data_object:
		 *  Loads a binary data object from a datafile.
		 */
		static private object load_data_object(PACKFILE f, int size)
		{
			byte[] buff = new byte[size];
			pack_fread(buff, size, f);

			return buff;
		}

		/* load_object:
		 *  Helper to load an object from a datafile and store it in 'obj'.
		 *  Returns 0 on success and -1 on failure.
		 */
		static private DATAFILE load_object(PACKFILE f, int type)
		{
			DATAFILE obj = new DATAFILE();

			int compressed = (int)pack_mgetl(f);
			int uncompressed = (int)pack_mgetl(f);

			// LZSS packed?
			if (uncompressed < 0)
				throw new Exception("Compressed file objects is not supported");

			try
			{
				// known type?
				if (datatypes_loader.ContainsKey(type))
					obj.dat = datatypes_loader[type](f, uncompressed);
				else
					obj.dat = load_data_object(f, uncompressed);
			}
			catch (Exception e)
			{
				System.Windows.MessageBox.Show(e.Message);
			}

			// error during loading?
			if (obj.dat == null)
			{
				obj.Dispose();
				obj = null;
				return null;
			}

			obj.type = type;
			obj.size = uncompressed;
			return obj;
		}

		/* load_file_objects:
		 *  Loads a datafile objects.
		 */
		static private DATAFILE[] load_file_objects(PACKFILE f, datafile_callback callback)
		{
			DATAFILE[] dat = null;
			List<DATAFILE_PROPERTY> list = null;

			int count = (int)pack_mgetl(f);
			int c, type;
			bool failed = false;

			dat = new DATAFILE[count + 1];

			// search the packfile for properties or objects
			for (c = 0; c < count; )
			{
				type = (int)pack_mgetl(f);

				if (type == DAT_PROPERTY)
				{
					if (list == null)
						list = new List<DATAFILE_PROPERTY>();

					// is property loaded?
					DATAFILE_PROPERTY property = load_property(f);
					if (property == null)
					{
						failed = true;
						break;
					}

					list.Add(property);
				}
				else
				{
					// is object loaded?
					DATAFILE obj = load_object(f, type);
					if (obj == null)
					{
						failed = true;
						break;
					}

					obj.prop = list;
					dat[c] = obj;
					list = null;

					if (callback != null)
						callback(obj);

					c++;
				}
			} // for(count)

			// add end-of-array marker
			dat[c] = new DATAFILE() { type = DAT_END, dat = null, prop = null, size = 0 };

			list = null;

			if (failed)
			{
				unload_datafile(dat);
				dat = null;
			}

			return dat;
		}

		/* destroy_basic_types:
		 * helper function for destroying known types
		 */
		static private void destroy_basic_types(object obj)
		{
			if (obj is RLE_SPRITE)
			{
				destroy_rle_sprite((RLE_SPRITE)obj);
				return;
			}

			if (obj is BITMAP)
			{
				destroy_bitmap((BITMAP)obj);
				return;
			}

			/*if (obj is SAMPLE)
			{
				destroy_sample((SAMPLE)obj);
				return;
			}*/
		}

		/* unload_datafile_object:
		 *  Unloads a single datafile object, returned by load_datafile_object().
		 */
		static private void unload_datafile_object(DATAFILE obj)
		{
			if (obj == null)
				return;

			if (datatypes_destroyer.ContainsKey(obj.type))
				datatypes_destroyer[obj.type](obj.dat);

			obj.Dispose();
		}

		/* unload_datafile:
		 *  Frees all the objects in a datafile.
		 */
		static public void unload_datafile(DATAFILE[] dat)
		{
			if (dat == null)
				return;

			int i_max = dat.Length;

			for (int i = 0; i < i_max; i++)
			{
				unload_datafile_object(dat[i]);
				dat[i] = null;
			}
		}

		/* load_datafile_object:
		 *  Loads a single object from a datafile.
		 *  
		 * TODO: Implement "parent#child" situation(nested files). need real of such usage examples :(
		 */
		static public DATAFILE load_datafile_object(string filename, string objectname)
		{
			DATAFILE dat = null;
			List<DATAFILE_PROPERTY> list = null;

			int c, size, type;

			if (filename.Length < 0 || objectname.Length < 0)
				return null;

			PACKFILE f = pack_fopen(filename, "r");

			if (f == null)
				return null;

			int count = (int)pack_mgetl(f);
			bool found = false;

			// search the packfile for properties or objects
			for (c = 0; c < count; )
			{
				type = (int)pack_mgetl(f);

				if (type == DAT_PROPERTY)
				{
					if (list == null)
						list = new List<DATAFILE_PROPERTY>();

					// is property loaded?
					DATAFILE_PROPERTY property = load_property(f);
					if (property == null)
						break;

					list.Add(property);

					if ((property.type == DAT_NAME) && (String.Compare(property.dat, objectname, StringComparison.OrdinalIgnoreCase) == 0))
						found = true;
				}
				else
				{
					if (found)
					{
						// we have found the object, load it
						dat = load_object(f, type);
						if (dat == null)
							break;

						dat.prop = list;
						list = null;
						break;
					}
					else
					{
						// skip an unwanted object
						size = (int)pack_mgetl(f);
						pack_fseek(f, size + 4); // '4' for the chunk size
						list = null;
						c++;
					}
				}
			}

			f.Dispose();
			list = null;
			return dat;
		}

		/* create_datafile_index
		 *  Reads offsets of all objects inside datafile.
		 *  On error, sets errno and returns NULL.
		 */
		static public DATAFILE_INDEX create_datafile_index(string _filename)
		{
			if (_filename.Length < 0)
				return null;

			PACKFILE f = pack_fopen(_filename, "r");

			if (f == null)
				return null;

			DATAFILE_INDEX index = null;

			long pos = 0;
			int skip, type;

			int count = (int)pack_mgetl(f); pos += 4;
			index = new DATAFILE_INDEX() { filename = _filename, offset = new long[count] };

			// search the packfile for properties or objects
			for (int i = 0; i < count; i++)
			{
				index.offset[i] = pos;
				type = (int)pack_mgetl(f); pos += 4;

				// Skip properties
				while (type == DAT_PROPERTY)
				{
					// Skip property name
					pack_fseek(f, 4); pos += 4;

					// Skip rest of property
					skip = (int)pack_mgetl(f); pos += 4;      // Get property size
					pack_fseek(f, skip); pos += skip;

					type = (int)pack_mgetl(f); pos += 4;
				}

				// Skip rest of object
				skip = (int)pack_mgetl(f) + 4; pos += 4;
				pack_fseek(f, skip); pos += skip;
			}

			f.Dispose();
			return index;
		}

		/* unload_datafile_index
		 *  DATAFILE_INDEX is pure managed object. 
		 *  So this function is just for compatibility with original library.
		 */
		static public void destroy_datafile_index(DATAFILE_INDEX index)
		{
			if (index != null)
			{
				index.filename = null;
				index.offset = null;
				index = null;
			}
		}

		/* load_datafile_object_indexed
		 *  Loads a single object from a datafile using its offset.
		 *  On error, returns NULL.
		 */
		static public DATAFILE load_datafile_object_indexed(DATAFILE_INDEX index, int item)
		{
			System.Diagnostics.Debug.Assert(index != null);

			if (index.filename == null || index.filename.Length < 0)
				return null;

			PACKFILE f = pack_fopen(index.filename, "r");
			List<DATAFILE_PROPERTY> list = null;

			DATAFILE dat;
			int type;

			pack_fseek(f, (int)index.offset[item]);

			do
			{
				type = (int)pack_mgetl(f);

				if (type != DAT_PROPERTY)
					break;

				if (list == null)
					list = new List<DATAFILE_PROPERTY>();

				// is property loaded?
				DATAFILE_PROPERTY property = load_property(f);
				if (property == null)
					break;

				list.Add(property);
			} while (type == DAT_PROPERTY);

			dat = load_object(f, type);

			// attach the property list to the object
			if (dat != null)
				dat.prop = list;

			list = null;
			f.Dispose();
			return dat;
		}

		/* get_datafile_property:
		 *  Returns the specified property string for the datafile object, or
		 *  an empty string if the property does not exist.
		 */
		static public string get_datafile_property(DATAFILE dat, int type)
		{
			string empty_string = "";

			if (dat == null || dat.prop == null)
				return empty_string;

			DATAFILE_PROPERTY prop;
			int i, i_max;

			for (i = 0, i_max = dat.prop.Count; i < i_max; i++)
			{
				prop = dat.prop[i];

				if (prop.type == DAT_END)
					return empty_string;

				if (prop.type == type)
					return (prop.dat != null) ? prop.dat : empty_string;

			}

			return empty_string;
		}

		/* find_datafile_object:
		 *  Returns a pointer to the datafile object with the given name
		 *  
		 * TODO: Implement "parent#child" situation(nested files). need real of such usage examples :(
		 */
		static public DATAFILE find_datafile_object(DATAFILE[] dat, string objectname)
		{
			if (dat == null || objectname.Length < 0)
				return null;

			string name;
			int pos;

			// search for the requested object
			for (pos = 0; dat[pos].type != DAT_END; pos++)
			{
				name = get_datafile_property(dat[pos], DAT_NAME);

				if (name != null && String.Compare(name, objectname, StringComparison.OrdinalIgnoreCase) == 0)
					return dat[pos];
			}

			// oh dear, the object isn't there...
			return null;
		}

		/* register_datafile_object: 
		 *  Registers a custom datafile object, providing functions for loading
		 *  and destroying the objects.
		 */
		static public void register_datafile_object(int id, datafile_object_loader_callback loader, datafile_object_destroyer_callback destroyer)
		{
			if (datatypes_loader.ContainsKey(id))
				datatypes_loader[id] = loader;
			else
				datatypes_loader.Add(id, loader);

			if (datatypes_destroyer.ContainsKey(id))
				datatypes_destroyer[id] = destroyer;
			else
				datatypes_destroyer.Add(id, destroyer);
		}
	}
}
